﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using SCGILite.Helper;

namespace SCGILite.NetLib
{
    class TcpSocketSession<TAppSession> : ISocketSession where TAppSession : IAppSession
    {
        public TAppSession appSession { get; private set; }

        Socket _socket;
        readonly SocketAsyncEventArgs _asyncR = new SocketAsyncEventArgs();
        readonly SocketAsyncEventArgs _asyncW = new SocketAsyncEventArgs();

        public IPEndPoint RemoteEndPoint { get; private set; }
        public IPEndPoint LocalEndPoint { get; private set; }

        bool _EnableRecive = false;
        public bool EnableRecive
        {
            get
            {
                return _EnableRecive;
            }
            set
            {
                if (_EnableRecive == value)
                    return;
                _EnableRecive = value;
                Receive();
            }
        }

        #region TcpDuplexAdapter
        public TcpSocketSession(Socket serverSocket)
        {
            _socket = serverSocket;
            RemoteEndPoint = serverSocket.RemoteEndPoint as IPEndPoint;
            LocalEndPoint = serverSocket.LocalEndPoint as IPEndPoint;

            _asyncR.Completed += (s, e) => { AsyncR_Completed(e); };
            _asyncW.Completed += (s, e) => { AsyncW_Completed(); };
        }
        #endregion

        #region Init
        public void Init(TAppSession se)
        {
            EnableRecive = true;
            appSession = se;
            Exception e = ActionHelper.SafeExcute(SocketHelper.TcpIOControl, _socket);
            if (e != null)
            {
                Close();
                appSession.Close();
            }
            else
            {
                appSession.StartSession();
                Receive();
            }
        }
        #endregion

        #region Close
        public bool isClose
        {
            get 
            {
                Socket sc = Interlocked.CompareExchange<Socket>(ref _socket, null, null);
                return (sc == null);
            }
        }
        public void Close()
        {
            Socket sc = Interlocked.Exchange<Socket>(ref _socket, null);
            if (sc == null)
                return;
            SocketHelper.SafeCloseSocket(sc);
            try
            {
                appSession.Close();
            }
            catch
            {
            }
        }
        #endregion

        #region Dispose
        public void Dispose()
        {
        }
        #endregion

        #region Completed
        private const string m_InvalidEndPoint = "0.0.0.0:0";
        void AsyncR_Completed(SocketAsyncEventArgs e)
        {
            try
            {
                int len = e.BytesTransferred;
                int offset = e.Offset;
                if ((len <= 0) || (e.SocketError != SocketError.Success))
                {
                    Close();
                }
                else
                {
                    ActionHelper.SafeExcute(appSession.OnReciveComplated, RBuff, offset, len);
                    Interlocked.CompareExchange(ref _onRecive, 0, 1);
                    Receive();
                }
            }
            catch
            {
                Close();
            }
        }
        void AsyncW_Completed()
        {
            try
            {
                if ((_asyncW.BytesTransferred > 0) && (_asyncW.SocketError == SocketError.Success))
                {
                    return;
                }
                else
                    Close();
            }
            catch
            {
                Close();
            }
            finally
            {
                _SendResponse();
            }
        }
        #endregion

        #region Receive
        byte[] RBuff = new byte[1024 * 5];
        int _onRecive = 0;
        public void Receive()
        {
            if ((isClose) || (!EnableRecive))
                return;
            int a = Interlocked.CompareExchange(ref _onRecive, 1, 0);
            if (a != 0)
                return;
            try
            {
                _asyncR.SetBuffer(RBuff, 0, RBuff.Length);
                bool f = _socket.ReceiveAsync(_asyncR);
                if (!f)
                {
                    AsyncR_Completed(_asyncR);
                }
            }
            catch
            {
                Close();
            }
        }
        #endregion

        #region SendBuff
        void SendBuff(int len)
        {
            if (len <= 0)
            {
                ChkWrite(true, null);
                return;
            }
            try
            {
                _asyncW.SetBuffer(sbuf, 0, len);
                bool f = _socket.SendAsync(_asyncW);
                if (!f)
                {
                    AsyncW_Completed();
                }
            }
            catch
            {
                closeStream(sendStream);
                this.Close();
            }
        }
        #endregion

        bool WriteBusy = false;
        object _lock = new object();

        #region Write Private
        Queue<INetOutputStream> link = new Queue<INetOutputStream>();
        byte[] sbuf = new byte[1024 * 8];
        INetOutputStream sendStream = null;
        #endregion

        #region closeSendStream
        void closeSendStream()
        {
            closeStream(Interlocked.Exchange(ref sendStream, null));
            if (link.Count == 0)
                return;
            INetOutputStream[] ss = null;
            lock (link)
            {
                ss = link.ToArray();
                link.Clear();
            }
            foreach (INetOutputStream s in ss)
            {
                closeStream(s);
            }
        }
        void closeStream(INetOutputStream s)
        {
            if (s == null)
                return;
            try
            {
                s.Close();
            }
            catch
            {
            }
        }
        #endregion

        #region _SendResponse
        void _SendResponse()
        {
            if (this.isClose)
            {
                closeSendStream();
                return;
            }
            int len = 0;
            try
            {
                if (sendStream.CanSyncRead)
                {
                    len = sendStream.Read(sbuf, 0, sbuf.Length);
                }
                else
                {
                    sendStream.BeginRead(sbuf, 0, sbuf.Length, SendBuff);
                    return;
                }
            }
            catch
            {
                ChkWrite(true, null);
            }
            SendBuff(len);
        }
        #endregion

        #region ChkWrite
        void ChkWrite(bool freeBusy, INetOutputStream data)
        {
            if (this.isClose)
            {
                closeStream(data);
                return;
            }
            INetOutputStream s = null;
            bool send = true;
            lock (_lock)
            {
                if (data != null)
                {
                    link.Enqueue(data);
                }
                if (freeBusy)
                    WriteBusy = false;
                if (WriteBusy)
                    return; 
                s = sendStream;
                sendStream = null;
                send = (link.Count != 0);
                if (send)
                {
                    WriteBusy = true;
                    sendStream = link.Dequeue();
                    link.TrimExcess();
                }
            }
            closeStream(s);
            if (send)
                _SendResponse();
        }
        #endregion

        #region SendResponse
        public void SendResponse(INetOutputStream data)
        {
            if (data == null)
                return;
            if (this.isClose)
            {
                closeStream(data);
                throw new Exception("Net Stream is close!!");
            }
            ChkWrite(false, data);
        }
        #endregion
    }
}
